package com.jeaven.classfile.classreader;

import com.jeaven.classfile.ClassFile;
import com.jeaven.classfile.attribute.*;
import com.jeaven.classfile.constantpool.ConstantPool;
import com.jeaven.classfile.constantpool.ConstantPoolInfo;
import com.jeaven.classfile.constantpool.ConstantPoolInfoEnum;
import com.jeaven.classfile.constantpool.content.*;
import com.jeaven.classfile.fields.Descriptor;
import com.jeaven.classfile.fields.Field;
import com.jeaven.classfile.fields.Fields;
import com.jeaven.classfile.interfaces.Interface;
import com.jeaven.classfile.interfaces.Interfaces;
import com.jeaven.classfile.methods.Method;
import com.jeaven.classfile.methods.Methods;
import com.jeaven.instruction.Instruction;
import com.jeaven.utils.Utils;
import com.jeaven.classfile.attribute.Line;
import com.jeaven.classfile.attribute.LineNumerTable;
import com.jeaven.classfile.attribute.BootstrapMethod;
import com.jeaven.classfile.attribute.BootstrapMethods;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.Exception;
import java.util.ArrayList;
import java.util.List;

;

// 从硬盘上读取字节码，转化为内存中的 classfile 类型数据，作为之后的 ClassLoader 的输入数据
// 可参考URL https://www.jianshu.com/p/238ef3ef82d7  
public class ClassReader {
    public ClassReader() {
        super();
    }

    public static ClassFile read(String classfilepath) throws IOException {
        DataInputStream is = new DataInputStream(new FileInputStream(classfilepath));
        return read(is);
    }

    public static ClassFile read(DataInputStream is) throws IOException {
        int magic = is.readInt();
        // System.out.println(Integer.toHexString(magic));
        int minorVersion = is.readUnsignedShort();
        int majorVersion = is.readUnsignedShort();

        int cpCount = is.readUnsignedShort();
        ConstantPool constantPool = readConstantPool(is, cpCount - 1);

        int accessFlag = is.readUnsignedShort();
        int thisClass = is.readUnsignedShort();
        int superClass = is.readUnsignedShort();

        int interfaceCount = is.readUnsignedShort();
        Interfaces interfaces = readInterfaces(is, interfaceCount, constantPool);

        int fieldCount = is.readUnsignedShort();
        Fields fields = readFields(is, fieldCount, constantPool);

        int methodCount = is.readUnsignedShort();
        Methods methods = readMethods(is, methodCount, constantPool);

        int attributeCount = is.readUnsignedShort();
        Attributes attributes = readAttributes(is, attributeCount, constantPool);

        return new ClassFile(
                magic,
                minorVersion,
                majorVersion,
                cpCount,
                constantPool,
                accessFlag,
                thisClass,
                superClass,
                interfaceCount,
                interfaces,
                fieldCount,
                fields,
                methodCount,
                methods,
                attributeCount,
                attributes
        );
    }

    // 读取字段表
    private static Fields readFields(DataInputStream is, int fieldCount, ConstantPool cp) throws IOException {
        Field[] fields = new Field[fieldCount];
        for(int i = 0; i < fieldCount; i++) {
            Field field = null;
            int accessFlag = is.readUnsignedShort();
            int nameIndex = is.readUnsignedShort();
            int descriptorIndex = is.readUnsignedShort();
            Descriptor descriptor = new Descriptor(Utils.getString(cp, descriptorIndex));
            int attributeCount = is.readUnsignedShort();
            String name = Utils.getString(cp, nameIndex);
            Attributes attributes = readAttributes(is, attributeCount, cp);
            field = new Field(accessFlag, name, descriptor, attributes);
            fields[i] = field;
        }
        return new Fields(fields);
    }

    // 读取方法表
    private static Methods readMethods(DataInputStream is, int methodCount, ConstantPool cp) throws IOException {
        Method[] methods = new Method[methodCount];
        for(int i = 0; i < methodCount; i++) {
            Method method = null;
            int accessFlag = is.readUnsignedShort();
            int nameIndex = is.readUnsignedShort();
            int descriptorIndex = is.readUnsignedShort();
            Descriptor descriptor = new Descriptor(Utils.getString(cp, descriptorIndex));
            int attributeCount = is.readUnsignedShort();
            String name = Utils.getString(cp, nameIndex);
            Attributes attributes = readAttributes(is, attributeCount, cp);
            method = new Method(accessFlag, name, descriptor, attributes);
            methods[i] = method;
        }
        return new Methods(methods);
    }

    // 读取接口索引
    private static Interfaces readInterfaces(DataInputStream is, int interfaceCount, ConstantPool cp) throws IOException {
        Interface[] interfaces = new Interface[interfaceCount];
        for (int i = 0; i < interfaceCount; i++) {
            int classIndex = is.readUnsignedShort();
            String className = Utils.getClassName(cp, classIndex);
            interfaces[i] = new Interface(className);
        }
        return new Interfaces(interfaces);
    }

    // 读取常量池
    private static ConstantPool readConstantPool(DataInputStream is, int cpCount) throws IOException {
        ConstantPool constantPool = new ConstantPool(cpCount);
        for (int i = 0; i < cpCount; i++) {
            int tag = is.readUnsignedByte();

            int infoEnum = tag;

            ConstantPoolInfo info = null;
            switch (infoEnum) {
                case ConstantPoolInfoEnum.CONSTANT_Fieldref:
                    info = new FieldRef(infoEnum, is.readUnsignedShort(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Methodref:
                    info = new MethodRef(infoEnum, is.readUnsignedShort(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_InterfaceMethodref:
                    info = new InterfaceMethodRef(infoEnum, is.readUnsignedShort(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_String:
                    info = new StringCp(infoEnum, is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Class:
                    info = new ClassCp(infoEnum, is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_NameAndType:
                    info = new NameAndType(infoEnum, is.readUnsignedShort(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Utf8:
                    int length = is.readUnsignedShort();
                    byte[] bytes = Utils.readNBytes(is, length);
                    info = new Utf8(infoEnum, bytes);
                    break;
                case ConstantPoolInfoEnum.CONSTANT_MethodHandle:
                    info = new MethodHandle(infoEnum, is.readUnsignedByte(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_MethodType:
                    info = new MethodType(infoEnum, is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_InvokeDynamic:
                    info = new InvokeDynamic(infoEnum, is.readUnsignedShort(), is.readUnsignedShort());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Integer:
                    info = new IntegerCp(infoEnum, is.readInt());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Long:
                    info = new LongCp(infoEnum, is.readLong());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Float:
                    info = new FloatCp(infoEnum, is.readFloat());
                    break;
                case ConstantPoolInfoEnum.CONSTANT_Double:
                    info = new DoubleCp(infoEnum, is.readDouble());
                    break;
            }
            if (info == null) {
                throw new UnsupportedOperationException("un parse cp " + infoEnum);
            }
            constantPool.infos[i] = info;
//            if (info.infoEnum == ConstantPoolInfoEnum.CONSTANT_Double
//                    || info.infoEnum == ConstantPoolInfoEnum.CONSTANT_Long) {
//                i++;
//            }
        }
        return constantPool;
    }

    // 读取属性表
    private static Attributes readAttributes(DataInputStream is, int attributeCount, ConstantPool cp) throws IOException {
        Attributes attributes = new Attributes(attributeCount);
        for(int i = 0; i < attributeCount; i++) {
            Attribute attribute = null;
            int attributeNameIndex = is.readUnsignedShort();
            String attributeName = Utils.getString(cp, attributeNameIndex);  // 属性名字
            int length = is.readInt(); // 属性长度
            // 下面只支持五种属性
            switch (attributeName) {
                case AttributeEnum.SourceFile:
                    int sourceFileIndex = is.readUnsignedShort();
                    String file = Utils.getString(cp, sourceFileIndex);
                    attribute = new SourceFile(file);
                    break;
                case AttributeEnum.Code:
                    int maxStack = is.readUnsignedShort();
                    int maxLocals = is.readUnsignedShort();
                    int codeLength = is.readInt();
                    byte[] byteCode = Utils.readNBytes(is, codeLength);
                    Instruction[] instructions = readByteCode(byteCode, cp);

                    int exceptionTableLength = is.readUnsignedShort();
                    com.jeaven.classfile.attribute.Exception[] exceptions = new com.jeaven.classfile.attribute.Exception[exceptionTableLength];
                    for (int i1 = 0; i1 < exceptionTableLength; i1++) {
                        int etsp = is.readUnsignedShort();
                        int etep = is.readUnsignedShort();
                        int ethp = is.readUnsignedShort();
                        int ctIdx = is.readUnsignedShort();
                        // null => catch any exception
                        String etClassname = null;
                        if (ctIdx != 0) {
                            etClassname = Utils.getClassName(cp, ctIdx);
                        }
                        com.jeaven.classfile.attribute.Exception exception = new com.jeaven.classfile.attribute.Exception(etsp, etep, ethp, etClassname);
                        exceptions[i1] = exception;
                    }
                    ExceptionTable exceptionTable = new ExceptionTable(exceptions);
                    int codeAttributeCount = is.readUnsignedShort();
                    Attributes codeAttributes = readAttributes(is, codeAttributeCount, cp);
                    attribute = new Code(maxStack, maxLocals, instructions, exceptionTable, codeAttributes);
                    break;
                case AttributeEnum.LineNumberTable:
                    int len = is.readUnsignedShort();
                    Line[] lines = new Line[len];
                    for (int i1 = 0; i1 < len; i1++) {
                        lines[i1] = new Line(is.readUnsignedShort(), is.readUnsignedShort());
                    }
                    attribute = new LineNumerTable(lines);
                    break;
                case AttributeEnum.BootstrapMethods:
                    int bsmLen = is.readUnsignedShort();
                    BootstrapMethod[] bootstrapMethods = new BootstrapMethod[bsmLen];
                    for (int i1 = 0; i1 < bsmLen; i1++) {
                        int bsmr = is.readUnsignedShort();
                        int nbma = is.readUnsignedShort();
                        Integer[] args = new Integer[nbma];
                        for (int i2 = 0; i2 < nbma; i2++) {
                            args[i2] = is.readUnsignedShort();
                        }
                        bootstrapMethods[i1] = new BootstrapMethod(bsmr, args);
                    }
                    attribute = new BootstrapMethods(bootstrapMethods);
                    break;
                default:
                    byte[] bytes = Utils.readNBytes(is, length);
            }
            attributes.attributes[i] = attribute;
        }
        return attributes;
    }

    // 读取属性表里的指令部分
    private static Instruction[] readByteCode(byte[] byteCode, ConstantPool constantPool) throws IOException {
        List<Instruction> instructions = new ArrayList<>();
        try(DataInputStream is = new DataInputStream(new ByteArrayInputStream(byteCode))) {
            while(is.available() > 0) {
                int opCode = is.readUnsignedByte();
                Instruction inst = InstructionReader.read(opCode, is, constantPool);
                if(inst == null) {
                    System.out.println(Integer.toHexString(opCode));
                    break;
                }
                instructions.add(inst);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Instruction[] res = new Instruction[instructions.size()];
        instructions.toArray(res);
        return res;
    }

}
